package com.nulldev.util.web.HttpServer3.pages.js_backend;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.function.Supplier;

import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptException;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.JVM.reflections.Reflections;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Charsets.CharsetUtil;
import com.nulldev.util.io.file.FileUtil;
import com.nulldev.util.osAPI.OS;
import com.nulldev.util.scripting.ScriptAPI.nScripting;
import com.nulldev.util.scripting.ScriptAPI.nashorn.polyfills.NashornPolyfiller;
import com.nulldev.util.web.HttpServer3.Response;
import com.nulldev.util.web.HttpServer3.pages.js_backend.impl.JSFormatterObject;
import com.nulldev.util.web.HttpServer3.pages.js_backend.impl.JSWindowObject;
import com.nulldev.util.web.HttpServer3.pages.js_backend.impl.JSXMLHttpRequest;
import com.nulldev.util.web.HttpServer3.pages.js_backend.impl.JSXMLHttpRequest.XMLHttpRequest;
import com.nulldev.util.web.HttpServer3.pages.v3.HttpPage3;
import com.nulldev.util.web.URLUtils.nStreamHandlerFactory.BlankInputStream;

public class JSBackedPage extends HttpPage3 {

	private final ScriptEngine se;
	private final Invocable se_invoc;
	private static final Class<?>[] DECL_INV_ARGS = new Class<?>[]
		{ String[].class };

	public JSBackedPage(final String id, final String payload) {
		super(id);
		Variables.requireNonNull(id, "id");
		final ScriptEngineFactory sef = nScripting.getJSEngine().getFactory();
		try {
			if (Reflections.is("jdk.nashorn.api.scripting.NashornScriptEngineFactory")
					&& Reflections.get("jdk.nashorn.api.scripting.NashornScriptEngineFactory").isInstance(sef)) {
				this.se = (ScriptEngine) Reflections.encapsulate(sef).invokeDecl("getScriptEngine", DECL_INV_ARGS, new Object[]
					{ new String[]
								{ "--language=es6", "--optimistic-types=true" } });
			} else {
				this.se = sef.getScriptEngine();
			}
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
		Variables.requireNonNull(this.se, "se");
		this.se_invoc = (Invocable) this.se;
		try {
			NashornPolyfiller.polyfill(this.se);
			this.init_js_env(this.se);
			this.se.eval(payload, this.se.getBindings(ScriptContext.ENGINE_SCOPE));
		} catch (ScriptException e) {
			throw new RuntimeException(e);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public Response onRequest(final Request request) {
		try {
			final Object obj = this.se_invoc.invokeFunction("onRequest", request);
			Variables.requireNonNull(obj, "request_obj");
			return (Response) obj;
		} catch (Exception ex) {
			JVM.errorln(ex);
			return new Response(503, FileUtil.getResourceStream(JSBackedPage.class, "js_backed_error.html"));
		}
	}

	public static class JSServerObject {

		public Response response(final int responseCode) {
			return new Response(responseCode);
		}

		public Response response(final int responseCode, final String text) {
			return new Response(responseCode, createStream0(text));
		}

	}

	private static final BlankInputStream bis0 = new BlankInputStream();
	private static final JSServerObject SERVER_UTILS = new JSServerObject();

	public static InputStream blankStream0() {
		return bis0;
	}

	private static InputStream createStream0(final String text) {
		if (text == null)
			return blankStream0();
		return new ByteArrayInputStream(OS.isWindows() ? text.getBytes(CharsetUtil.UTF_8) : text.getBytes());
	}

	private void init_js_env(final ScriptEngine se) {
		final Bindings bds = se.createBindings();
		bds.put("version", "1.0.0");
		bds.put("server", SERVER_UTILS);
		bds.put("formatter", JSFormatterObject.SERVER_FORMATTER);
		bds.put("window", JSWindowObject.SERVER_WINDOW);
		bds.put("XMLHttpRequest", new Supplier<XMLHttpRequest>() {

			@Override
			public XMLHttpRequest get() {
				return JSXMLHttpRequest.SERVER_XML.XMLHttpRequest();
			}
		});
		se.setBindings(bds, ScriptContext.ENGINE_SCOPE);
	}
}
